import requests
import time
import csv
import tkinter as tk
from tkinter import messagebox, ttk, scrolledtext

def has_remainder(a, b):
    return a % b != 0  # 有余数返回True，否则False

def validate_inputs():
    """校验所有必填字段"""
    # 获取各控件值
    cookie = entry_cookie.get('1.0', tk.END).strip()
    keyword = entry_word.get().strip()
    days = combo_days.get()
    
    # 校验逻辑
    if not cookie:
        messagebox.showerror("错误", "小红书Cookie不能为空！")
        entry_cookie.focus_set()
        return False
    
    if not keyword:
        messagebox.showerror("错误", "搜索关键词不能为空！")
        entry_word.focus_set()
        return False
    
    if not days:
        messagebox.showerror("错误", "请选择最近天数！")
        combo_days.focus_set()
        return False
    
    return True


def run_spider():
    if not validate_inputs():
        return  # 校验失败则停止执行

    btn_run.config(state=tk.DISABLED)
    # 创建进度条
    progress_var = tk.DoubleVar()
    progress_bar = ttk.Progressbar(root, variable=progress_var, maximum=100, length=280)
    progress_bar.grid(row=4, columnspan=2, pady=10)
    
    # 创建进度标签
    progress_label = tk.Label(root, text="准备爬取...")
    progress_label.grid(row=5, columnspan=2)
    root.update_idletasks()

    searchWord = entry_word.get()
    nd = combo_days.get()  ## 查询最近多少天数
    cookie = {
        'Cookie': entry_cookie.get('1.0', tk.END).strip()
    }

    formal_page_size = 50

    header = {
        'X-S':'XYW_eyJzaWduU3ZuIjoiNTYiLCJzaWduVHlwZSI6IngyIiwiYXBwSWQiOiJjcmVhdGl2aXR5LWNlbnRlciIsInNpZ25WZXJzaW9uIjoiMSIsInBheWxvYWQiOiIxOWM2ZmEzYTA2MGU5NTVlMDMwYmUxYjgzYzZkMmRjNjk1NTcwNjVkMWZlNDdkYjMwZjFiYmVkNDU3YWJmOTdlZTlkODJhZjdhZmVhZTgxMDUxZTRjM2E0OTI1YzM2NzFjNmQwMmYxOTgzNjRlMmQ1OTliYWRiMDgzNzNkMzZiYjg5ZjBiMGFmMDc0NDlkODBlNjc3ZWJhMTAzN2RmNzAzNTRiOTc2MTgwNjQwMzFmYjhjM2ZkZTllN2I5Y2I0ODJlNzcyODk2NTU5YTVmYzIwYjdhNzFkNzRjODZjMDQwY2U2MmZiNmNmNjllYjJlZDNjNzhlYWU0OGIwZDVlOGJiMDYzYTQyZjNlMjRkZWVmMTM0ODU4ZWQyMjEzMWI5ZjNmNDU0NGVmMDRmYWE0MzkyNjg3MjM2NjgzMDc4YWUxN2ZhMTQzOWE3YmQ4YTRiY2UwYTU4YmQ5YTA1OGY0MTNhOWQxY2UyMjQzY2I2N2NlMjNmZDA0ODNmM2NhZTIxZWViN2RmN2ZlZDg2NWRmZmNkMWIxMGYxNzQ1ZTdiNzY1NSJ9',
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0',
        'Referer': 'https://pgy.xiaohongshu.com/',
        'Origin': 'https://pgy.xiaohongshu.com',
        'Content-Type': 'application/json;charset=UTF-8',
        'authority': 'edith.xiaohongshu.com',
        'x-t': '1743833181889'
    }

    data_url = 'https://edith.xiaohongshu.com/api/pgy/content_square/search_note_v2'

    data = {
        'bizType': "4",
        'nd': nd,
        'orderBy': "premium_good_read_rate",
        'pageNum': 1,
        'pageSize': 1,
        'searchWord': searchWord
    }

    session = requests.session()
    res = session.post(url=data_url, headers=header, json=data, cookies=cookie)
    res_dict = res.json()

    if not res_dict.get('success'):
        print(f"请求失败: {res_dict.get('msg')}")
        messagebox.showerror("请求失败", res_dict.get('msg'))

    return_note_total = res_dict.get('data').get('total')
    if return_note_total == 0:
        messagebox.showinfo("提示", "该关键词搜索数量为0，请更换关键词重新搜索")

    progress_label.config(text=f"搜索到{return_note_total}条数据, 开始爬取")
    root.update_idletasks()
    
    page_divisor = int(return_note_total / formal_page_size)
    count_num = (page_divisor) + 1 if has_remainder(return_note_total, formal_page_size) else (page_divisor)

    final_notelist = []

    try:
        for i in range(count_num):
            data['pageNum'] = i+1
            data['pageSize'] = formal_page_size
            
            response = session.post(url=data_url, headers=header, json=data, cookies=cookie)
            response_json = response.json()
            noteList = response_json.get('data').get('noteList')
            if noteList:  # 检查 noteList 非空
                final_notelist += noteList

                progress_label.config(text=f"正在爬取第{i+1}页数据")
                progress_var.set((i+1) / 4 * 100)
                root.update_idletasks()
            time.sleep(1)
    except Exception as e:
        print(f"第 {i+1} 页解析异常:", e)
        messagebox.showerror("请求失败", f"第 {i+1} 页解析异常:{e}")

    like_stats = {
        "0-999": {"count": 0, "links": []},
        "1000-2999": {"count": 0, "links": []},
        "3000-9999": {"count": 0, "links": []},
        "10000-29999": {"count": 0, "links": []},
        "≥30000": {"count": 0, "links": []}
    }

    progress_label.config(text=f"正在准备写入CSV文件...")
    progress_var.set(60)
    root.update_idletasks()

    try:
        with open(f'爆文点赞层级_{searchWord}_近{nd}天.csv', 'w+', encoding='utf-8-sig', newline='') as f:
            writer = csv.writer(f)
            writer.writerow(["标题", "点赞数量", "链接"])  # 写入列名
            
            progress_label.config(text=f"正在准备写入列名到文件...")
            progress_var.set(70)
            root.update_idletasks()

            final_notelist_num = len(final_notelist)

            for index, note in enumerate(final_notelist):
                print(f"final_notelist:{final_notelist}")
                noteinfo = note.get('noteInfo', {})
                title = noteinfo.get('title')
                likeNum = noteinfo.get('likeNum')
                noteLink = noteinfo.get('noteLink')
                
                writer.writerow([title, likeNum, noteLink])
                
                progress_label.config(text=f"正在准备写入第{index+1}条到文件...")
                progress_var.set(70+ (index+1) / final_notelist_num * 20)
                root.update_idletasks()

                # 判断区间并统计
                if likeNum >= 30000:
                    like_stats["≥30000"]["count"] += 1
                    like_stats["≥30000"]["links"].append(noteLink)  # 保存链接
                elif likeNum >= 10000:
                    like_stats["10000-29999"]["count"] += 1
                    like_stats["10000-29999"]["links"].append(noteLink)
                elif likeNum >= 3000:
                    like_stats["3000-9999"]["count"] += 1
                    like_stats["3000-9999"]["links"].append(noteLink)
                elif likeNum >= 1000:
                    like_stats["1000-2999"]["count"] += 1
                    like_stats["1000-2999"]["links"].append(noteLink)
                else:
                    like_stats["0-999"]["count"] += 1

            writer.writerow([])  # 空行分隔
            writer.writerow([])  # 空行分隔
            writer.writerow([])  # 空行分隔

            writer.writerow(["点赞分层", "数量", "对应链接"])

            for range_name, data in like_stats.items():
                count = data.get('count')
                links_str = ', '.join(data['links'])
                writer.writerow([range_name, count, links_str])

        progress_label.config(text=f"写入完成!")
        progress_var.set(100)
        btn_run.config(state=tk.NORMAL)
    
    except PermissionError:
        messagebox.showerror("写入失败", f"当前写入文件被占用，需要关闭文件")

        progress_var.set(0)
        progress_label.config(text="等待开始...")
        btn_run.config(state=tk.NORMAL)

    except Exception as e:
        messagebox.showerror("错误", f"保存失败: {str(e)}")

root = tk.Tk()
root.title("小红书爆文点赞自动分层工具")
root.geometry("380x350")  # 设置窗口宽度400像素，高度300像素
# 输入控件

tk.Label(root, text="小红书Cookie:", fg="red").grid(row=0, column=0, padx=5, pady=5)
entry_cookie = scrolledtext.ScrolledText(root, width=30, height=10)
entry_cookie.grid(row=0, column=1, padx=5, pady=5, sticky="ew")

tk.Label(root, text="搜索关键词:").grid(row=1, column=0, padx=5, pady=5)
entry_word = tk.Entry(root, width=30)
entry_word.insert(1, "Ulike")
entry_word.grid(row=1, column=1, padx=5, pady=5, sticky="ew")

days_options = ["7", "3", "14", "30"]  # 可选项列表
tk.Label(root, text="最近天数:").grid(row=2, column=0, padx=5, pady=5)
combo_days = ttk.Combobox(root, values=days_options, width=28)  # width单位是字符数
combo_days.grid(row=2, column=1, padx=5, pady=5, sticky="ew")
combo_days.current(3)  # 默认选中30天

# 运行按钮
btn_run = tk.Button(root, text="开始爬取", command=run_spider)
btn_run.grid(row=3, columnspan=2, pady=10)

root.mainloop()